require 'msf/core/module'

module Msf
  class Evasion < Msf::Module

    include Msf::Auxiliary::Report

    class Complete < RuntimeError ; end

    class Failed < RuntimeError ; end

    def initialize(info={})
      if (info['Payload'] and info['Payload']['Compat'])
        info['Compat'] = Hash.new if (info['Compat'] == nil)
        info['Compat']['Payload'] = Hash.new if (info['Compat']['Payload'] == nil)
        info['Compat']['Payload'].update(info['Payload']['Compat'])
      end

      super(info)

      self.payload_info = info['Payload'] || {}
      self.targets = Rex::Transformer.transform(info['Targets'], Array, [ Target ], 'Targets')

      if info.key? 'DefaultTarget'
        self.default_target = info['DefaultTarget']
      else
        self.default_target = 0
        # Add an auto-target to the evasion if it doesn't have one
        if info['Targets'] && info['Targets'].count > 1 && !has_auto_target?(info['Targets'])
          # Finally, only add the target if there is a remote host option
          if self.respond_to?(:rhost) && self.respond_to?(:auto_targeted_index)
            auto = ["Automatic", {'AutoGenerated' => true}.merge(info['Targets'][self.default_target][1])]
            info['Targets'].unshift(auto)
          end
        end
      end

      if (info['Payload'] and info['Payload']['ActiveTimeout'])
        self.active_timeout = info['Payload']['ActiveTimeout'].to_i
      end

      register_options([
        OptString.new(
          'FILENAME',
            [
              true,
              'Filename for the evasive file (default: random)',
              "#{Rex::Text.rand_text_alpha(3..10)}.exe"
            ])
      ], self.class)
    end

    def self.type
      Msf::MODULE_EVASION
    end

    def type
      Msf::MODULE_EVASION
    end

    def setup
    end

    def file_format_filename
      datastore['FILENAME']
    end

    def file_create(data)
      fname = file_format_filename
      ltype = "evasion.fileformat.#{self.shortname}"
      full_path = store_local(ltype, nil, data, fname)
      print_good "#{fname} stored at #{full_path}"
    end

    #
    # Returns the target's platform, or the one assigned to the module itself.
    #
    def target_platform
      (target and target.platform) ? target.platform : platform
    end

    #
    # Returns the target's architecture, or the one assigned to the module
    # itself.
    #
    def target_arch
      (target and target.arch) ? target.arch : (arch == []) ? nil : arch
    end

    def normalize_platform_arch
      c_platform = (target && target.platform) ? target.platform : platform
      c_arch     = (target && target.arch)     ? target.arch     : (arch == []) ? nil : arch
      c_arch   ||= [ ARCH_X86 ]
      return c_platform, c_arch
    end

    # Returns whether the requested payload is compatible with the module
    #
    # @param [String] name The payload name
    # @param [TrueClass] Payload is compatible.
    # @param [FlaseClass] Payload is not compatible.
    def is_payload_compatible?(name)
      p = framework.payloads[name]

      pi = p.new

      # Are we compatible in terms of conventions and connections and
      # what not?
      return false if !compatible?(pi)

      # If the payload is privileged but the evasion does not give
      # privileged access, then fail it.
      return false if !self.privileged && pi.privileged

      return true
    end

    # Returns a list of compatible payloads based on platform, architecture,
    # and size requirements.
    def compatible_payloads
      payloads = []

      c_platform, c_arch = normalize_platform_arch

      framework.payloads.each_module(
        'Arch' => c_arch, 'Platform' => c_platform) { |name, mod|
        payloads << [ name, mod ] if is_payload_compatible?(name)
      }

      return payloads
    end

    def run
      raise NotImplementedError
    end

    def cleanup
    end

    def fail_with(reason, msg=nil)
      raise Msf::Evasion::Failed, "#{reason}: #{msg}"
    end

    def evasion_commands
      {}
    end

    def stance
      'passive'
    end

    def passive?
      true
    end

    def aggressive?
      false
    end

    # Generates the encoded version of the supplied payload using the payload
    # requirements specific to this evasion module. The encoded instance is returned
    # to the caller. This method is exposed in the manner that it is such that passive
    # evasions and re-generate an encoded payload on the fly rather than having to use
    # the pre-generated one.
    def generate_payload(pinst = nil)
      # Set the encoded payload to the result of the encoding process
      self.payload = generate_single_payload(pinst)

      # Save the payload instance
      self.payload_instance = (pinst) ? pinst : self.payload_instance

      return self.payload
    end

    def generate_single_payload(pinst = nil, platform = nil, arch = nil, explicit_target = nil)
      explicit_target ||= target

      # If a payload instance was supplied, use it, otherwise
      # use the active payload instance
      real_payload = (pinst) ? pinst : self.payload_instance

      if (real_payload == nil)
        raise MissingPayloadError, "No payload has been selected.",
          caller
      end

      # If this is a generic payload, then we should specify the platform
      # and architecture so that it knows how to pass things on.
      if real_payload.kind_of?(Msf::Payload::Generic)
        # Convert the architecture specified into an array.
        if arch and arch.kind_of?(String)
          arch = [ arch ]
        end

        # Define the explicit platform and architecture information only if
        # it's been specified.
        if platform
          real_payload.explicit_platform = Msf::Module::PlatformList.transform(platform)
        end

        if arch
          real_payload.explicit_arch = arch
        end

        # Force it to reset so that it will find updated information.
        real_payload.reset
      end

      # Duplicate the evasion payload requirements
      reqs = self.payload_info.dup

      # Pass save register requirements to the NOP generator
      reqs['Space']           = payload_info['Space'].to_i
      reqs['SaveRegisters']   = module_info['SaveRegisters']
      reqs['Prepend']         = payload_info['Prepend']
      reqs['PrependEncoder']  = payload_info['PrependEncoder']
      reqs['BadChars']        = payload_info['BadChars']
      reqs['Append']          = payload_info['Append']
      reqs['AppendEncoder']   = payload_info['AppendEncoder']
      reqs['MaxNops']         = payload_info['MaxNops']
      reqs['MinNops']         = payload_info['MinNops']
      reqs['Encoder']         = datastore['ENCODER'] || payload_info['Encoder']
      reqs['Nop']             = datastore['NOP'] || payload_info['Nop']
      reqs['EncoderType']     = payload_info['EncoderType']
      reqs['EncoderOptions']  = payload_info['EncoderOptions']
      reqs['ExtendedOptions'] = payload_info['ExtendedOptions']
      reqs['Evasion']         = self

      # Pass along the encoder don't fall through flag
      reqs['EncoderDontFallThrough'] = datastore['EncoderDontFallThrough']

      # Incorporate any context encoding requirements that are needed
      define_context_encoding_reqs(reqs)

      # Call the encode begin routine.
      encode_begin(real_payload, reqs)

      # Generate the encoded payload.
      encoded = EncodedPayload.create(real_payload, reqs)

      # Call the encode end routine which is expected to return the actual
      # encoded payload instance.
      return encode_end(real_payload, reqs, encoded)
    end

    def define_context_encoding_reqs(reqs)
      return unless datastore['EnableContextEncoding']

      # At present, we don't support any automatic methods of obtaining
      # context information.  In the future, we might support obtaining
      # temporal information remotely.

      # Pass along the information specified in our evasion datastore as
      # encoder options
      reqs['EncoderOptions'] = {} if reqs['EncoderOptions'].nil?
      reqs['EncoderOptions']['EnableContextEncoding']  = datastore['EnableContextEncoding']
      reqs['EncoderOptions']['ContextInformationFile'] = datastore['ContextInformationFile']
    end

    def encode_begin(real_payload, reqs)
    end

    def encode_end(real_payload, reqs, encoded)
      encoded
    end

    def target
      if self.respond_to?(:auto_targeted_index)
        if auto_target?
          auto_idx = auto_targeted_index
          if auto_idx.present?
            datastore['TARGET'] = auto_idx
          else
            # If our inserted Automatic Target was selected but we failed to
            # find a suitable target, we just grab the original first target.
            datastore['TARGET'] = 1
          end
        end
      end

      target_idx = target_index
      return (target_idx) ? targets[target_idx.to_i] : nil
    end

    def target_index
      target_idx = Integer(datastore['TARGET']) rescue datastore['TARGET']

      default_idx = default_target || 0
      # Use the default target if one was not supplied.
      if (target_idx == nil and default_idx and default_idx >= 0)
        target_idx = default_idx
      elsif target_idx.is_a?(String)
        target_idx = targets.index { |target| target.name == target_idx }
      end

      return (target_idx) ? target_idx.to_i : nil
    end

    def has_auto_target?(targets=[])
      target_names = targets.collect { |target| target.first}
      target_names.each do |target|
        return true if target =~ /Automatic/
      end
      return false
    end

    attr_accessor :default_target

    attr_accessor :targets

    attr_reader :payload_info

    attr_accessor :payload_info

    attr_accessor :payload_instance

    attr_accessor :payload
  end
end